package com.googlecode.lookie.preview;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;

import javax.swing.JComponent;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;

import com.googlecode.lookie.preview.ImagePreviewOptions.FitMode;

public class ImagePreview
    extends JComponent
    implements ComponentListener, AncestorListener, ImagePreviewModelListener
{

    private static final long serialVersionUID = 7207001657325374479L;

    private ImagePreviewModel model;

    private ImagePreviewOptions options = new ImagePreviewOptions();

    private Dimension imageSize;

    public ImagePreview()
    {
        addComponentListener( this );
        addAncestorListener( this );
        model = new DefaultImagePreviewModel();
        model.addListener( this );
    }

    public ImagePreviewModel getModel()
    {
        return model;
    }

    public void setModel( ImagePreviewModel model )
    {
        this.model.removeListener( this );
        this.model = model;
        this.model.addListener( this );
    }

    public ImagePreviewOptions getOptions()
    {
        return options;
    }

    public void setOptions( ImagePreviewOptions options )
    {
        this.options = options;
    }

    public void paint( Graphics g )
    {
        if ( model.getImageSource() != null && model.getImageSource().getImage() != null )
        {
            Image image = model.getImageSource().getImage();
            int h = image.getHeight( this );
            int w = image.getWidth( this );
            Rectangle dest = calculateDestination();

            g.drawImage( image, dest.x, dest.y, dest.width, dest.height, 0, 0, w, h, this );
        }
    }

    private Dimension calculatePreferredSize()
    {
        Dimension d = new Dimension();

        Rectangle vis = getVisibleRect();

        if ( model.getImageSource() != null && model.getImageSource().getImage() != null )
        {
            Image image = model.getImageSource().getImage();

            int h = image.getHeight( this );
            int w = image.getWidth( this );

            Insets i = getInsets();

            if ( vis.height < h + i.top + i.bottom || vis.width < w + i.left + i.right )
            {
                switch ( getOptions().getFitMode() )
                {
                    case NO_FIT:
                        d.height = h + i.top + i.bottom;
                        d.width = w + i.left + i.right;

                        imageSize = new Dimension( w, h );
                        break;
                    default:
                        d.height = vis.height;
                        d.width = vis.width;

                        imageSize = calculateResizedUp( vis, w, h, i );

                        break;
                }
            }
            else
            {
                d.height = vis.height;
                d.width = vis.width;
                if ( getOptions().getFitMode() == FitMode.FIT_ALL )
                {
                    imageSize = calculateResizedDown( vis, w, h, i );
                }
                else
                {
                    imageSize = new Dimension( w, h );
                }
            }
        }
        else
        {
            d.height = vis.height;
            d.width = vis.width;

            imageSize = null;
        }

        return d;
    }

    private Dimension calculateResizedDown( Rectangle vis, int w, int h, Insets i )
    {
        Dimension d = new Dimension();

        int avH = vis.height - i.top - i.bottom;
        int avW = vis.width - i.left - i.right;

        double factor = 0;

        if ( w < avW )
        {
            double fW = (double) w / (double) avW;
            if ( h < avH )
            {
                double fH = (double) h / (double) avH;
                factor = Math.max( fW, fH );
            }
            else
            {
                factor = fW;
            }
        }
        else
        {
            double fH = (double) h / (double) avH;
            if ( w < avW )
            {
                double fW = (double) w / (double) avW;
                factor = Math.max( fW, fH );
            }
            else
            {
                factor = fH;
            }
        }

        d.width = (int) ( w / factor );
        d.height = (int) ( h / factor );

        return d;
    }

    private Dimension calculateResizedUp( Rectangle vis, int w, int h, Insets i )
    {
        Dimension d = new Dimension();

        int avH = vis.height - i.top - i.bottom;
        int avW = vis.width - i.left - i.right;

        double factor = 0;

        if ( w > avW )
        {
            double fW = (double) w / (double) avW;
            if ( h > avH )
            {
                double fH = (double) h / (double) avH;
                factor = Math.max( fW, fH );
            }
            else
            {
                factor = fW;
            }
        }
        else
        {
            double fH = (double) h / (double) avH;
            if ( w > avW )
            {
                double fW = (double) w / (double) avW;
                factor = Math.max( fW, fH );
            }
            else
            {
                factor = fH;
            }
        }

        d.width = (int) ( w / factor );
        d.height = (int) ( h / factor );

        return d;
    }

    private Rectangle calculateDestination()
    {
        Rectangle dest = new Rectangle();

        Insets i = getInsets();

        dest.x = i.left;
        dest.y = i.top;

        dest.width = imageSize.width;
        dest.height = imageSize.height;

        return dest;
    }

    public void componentHidden( ComponentEvent e )
    {
        // TODO Auto-generated method stub

    }

    public void componentMoved( ComponentEvent e )
    {
        // TODO Auto-generated method stub

    }

    public void componentResized( ComponentEvent e )
    {
        setPreferredSize( calculatePreferredSize() );
    }

    public void componentShown( ComponentEvent e )
    {

    }

    public void ancestorAdded( AncestorEvent event )
    {
        getParent().addComponentListener( this );
        // getTopLevelAncestor().addComponentListener(this);
    }

    @Override
    public Dimension getPreferredSize()
    {
        return super.getPreferredSize();
    }

    public void ancestorMoved( AncestorEvent event )
    {
        // TODO Auto-generated method stub

    }

    public void ancestorRemoved( AncestorEvent event )
    {
        getParent().removeComponentListener( this );
    }

    public void imageChanged( ImageChangedEvent event )
    {
        setPreferredSize( calculatePreferredSize() );
        repaint();
    }

}
